#include "FileReader.hpp"
#include "Debug.hpp"

#include <string.h>
#include <ctype.h>
#include <algorithm>

std::vector<std::string> tokenize(const char* str, const char* delim)
{ 
    std::vector<std::string> tokens;
    char* token = strtok (const_cast<char*>(str), const_cast<char*>(delim));
    while (token != NULL) {
        tokens.push_back(token);
        token = strtok (NULL, delim);
    }
    return tokens;
}

void CapitalizeString(std::string& toDecap)
{
    std::string::iterator it=toDecap.begin();
    std::string::iterator end_it=toDecap.end();
    for(;it!=end_it;++it)
        *it=toupper(*it);
}

/*It returns the type of the parameter but incase it does not find
* the key it will return a undefine_type
*/
Param_Type FileReader::GetParamType(const std::string& key) const
{
    std::vector<key_struct>::const_iterator it=keys_.begin(), end_it=keys_.end(); 
    for(;it!=end_it; ++it)
    {
        if(it->parameter_name==key)
        {
            return it->ptype;
        }
    }
    return Param_type_UnDef;
}

void FileReader::RegisterIntParameter(const std::string& key, int init)
{
    std::map<std::string,int>::const_iterator it=int_params_.find(key),end_it=int_params_.end();
    if(it==end_it)
    {
        key_struct toRegister={key,Param_type_int};
        keys_.push_back(toRegister);
        parameter_names_.push_back(key);
        int_params_.insert(std::pair<std::string,int>(key,init));
    }
    else
    {
        std::string message= "The parameter " + key + " was previously registered.";
        ASSERT(false, message.c_str());
    }
}

void FileReader::RegisterDoubleParameter(const std::string& key, double init)
{
    std::map<std::string,double>::const_iterator it=double_params_.find(key),end_it=double_params_.end();
    if(it==end_it)
    {
        key_struct toRegister={key,Param_type_double};
        keys_.push_back(toRegister);
        parameter_names_.push_back(key);
        double_params_.insert(std::pair<std::string,double>(key,init));
    }
    else
    {
        std::string message= "The parameter " + key + " was previously registered.";
        ASSERT(false, message.c_str());
    }
}

void FileReader::RegisterStringParameter(const std::string& key, const std::string &init)
{
    std::map<std::string,std::string>::const_iterator it=string_params_.find(key),end_it=string_params_.end();
    if(it==end_it)
    {
        key_struct toRegister={key,Param_type_string};
        keys_.push_back(toRegister);
        parameter_names_.push_back(key);
        string_params_.insert(std::pair<std::string,std::string>(key,init));
    }
    else
    {
        std::string message= "The parameter " + key + " was previously registered.";
        ASSERT(false, message.c_str());
    }
}

void FileReader::PrintKeyTuples(std::ostream& out) const
{
    std::vector<key_struct>::const_iterator it=keys_.begin(), end_it=keys_.end();
    out<<"#Registered key tuples [keyname , type]\n";
    for(;it!=end_it; ++it)
        out <<"["<<it->parameter_name << ","<<ParamTypeToString(it->ptype)<<"]\n";
    out << "\n";
}

void FileReader::PrintParameters(std::ostream& out) const
{
    std::map<std::string,int>::const_iterator it_int=int_params_.begin(),end_it_int=int_params_.end();
    std::map<std::string,double>::const_iterator it_double=double_params_.begin(),end_it_double=double_params_.end();
    std::map<std::string,std::string>::const_iterator it_string=string_params_.begin(),end_it_string=string_params_.end();

    out<<"#List of Parameters [parameter_name , parameter_value]\n";
    for(;it_string!=end_it_string;++it_string)
        out<<"["<<it_string->first<<","<<it_string->second<<"]\n";
    for(;it_double!=end_it_double;++it_double)
        std::cout<<"["<<it_double->first<<","<<it_double->second<<"]\n";
    for(;it_int!=end_it_int;++it_int)
        std::cout<<"["<<it_int->first<<","<<it_int->second<<"]\n";
    out << "\n";
}

void FileReader::SetParameter(const std::string &key, const std::string &in)
{
    std::map<std::string,std::string>::iterator it=string_params_.find(key),end_it=string_params_.end();
    if(it==end_it)
    {
        std::string er_key=key;
        std::string message= "the parameter " + er_key + " is not registered as an input parameter";
        WARN(message.c_str());
    }
    else
        it->second=in;
}
void FileReader::SetParameter(const std::string &key, double in)
{
    std::map<std::string,double>::iterator it=double_params_.find(key),end_it=double_params_.end();
    if(it==end_it)
    {
        std::string message= "the parameter " + key + " is not registered as an input parameter";
        WARN(message.c_str());
    }
    else
        it->second=in;
}

void FileReader::SetParameter(const std::string &key, int in)
{
    std::map<std::string,int>::iterator it=int_params_.find(key),end_it=int_params_.end();
    if(it==end_it)
    {
        std::string message= "the parameter " + key + " is not registered as an input parameter";
        WARN(message.c_str());
    }
    else
        it->second=in;
}

cl_int FileReader::GetIntParameter(const std::string &key) const
{
    std::map<std::string,int>::const_iterator it=int_params_.find(key),end_it=int_params_.end();
    if(it==end_it)
    {
        //ASSERTION DEBUG
        std::string message= "the parameter " + key + " was not previously registered";
        ASSERT(false, message.c_str());
    }
    return it->second;
}

cl_real FileReader::GetDoubleParameter(const std::string &key) const
{
    std::map<std::string,double>::const_iterator it=double_params_.find(key),end_it=double_params_.end();
    if(it==end_it)
    {
        //ASSERTION DEBUG
        std::string message= "the parameter " + key + " was not previously registered";
        ASSERT(false, message.c_str());
    }
    return it->second;
}

std::string FileReader::GetStringParameter(const std::string &key) const
{
    std::map<std::string,std::string>::const_iterator it=string_params_.find(key),end_it=string_params_.end();
    if(it==end_it)
    {
        //ASSERTION DEBUG
        std::string message= "the parameter " + key + " was not previously registered";
        ASSERT(false, message.c_str());
    }
    return it->second;
}


//It assume that the value parsed is the right of type!!!!!
bool FileReader::ReadFile(const std::string &name)
{
    std::ifstream infile (name.c_str(), std::ios::in);
    const int max_chars_per_line = 512;
    char *buffer(new char[max_chars_per_line]);
    std::vector<std::string> tokens;
    int n_tokens;
    int counter=1;
    bool reading=true, empty=true;
    Param_Type pType=Param_type_UnDef;
    std::string error_message("");

    if(!(infile.is_open() && infile.good()))
    {
        std::cerr << std::endl;    
        std::cerr << "ERROR: Failed to open file: " << name << std::endl;
        std::cerr << std::endl;
        reading=false;
        exit(1);
    }
    while(infile.good())
    {	  
            infile.getline(buffer, max_chars_per_line);
            tokens = tokenize(buffer, " \t\r\n");
            n_tokens=tokens.size();
    
            //allow for empty lines
            if (n_tokens == 0) {
                ++counter;
                continue;
            } // and allow for comments
            else if (tokens[0][0] == '#') {
                ++counter;
                continue;
            }
            // check for parameter with no value
            if(n_tokens==1)
            {
                std::cout<<"Error: parameter "<<tokens[0]<<" Does not has a specified value. Check line "<<counter<<" of your parameters file.";
                reading=false;	    
                exit(1);
            }
            else if(n_tokens>=2)
            {
                empty=false;
                std::string concatenator("");
                std::string paramName = tokens[0];
                CapitalizeString(paramName);
                for(int j=1;j<n_tokens && tokens[j][0]!='#';j++)   
                {	
                    concatenator += " " + tokens[j];  
                }
                pType=GetParamType(paramName);
                if(pType==Param_type_int)
                {
                    int value = atoi(tokens[1].c_str());
                    SetParameter(paramName,value);
                }
                else if(pType==Param_type_double)
                {
                    double value = atof(tokens[1].c_str());
                    SetParameter(paramName,value);
                }
                else if(pType==Param_type_string)
                {
                    std::string value = concatenator;
		    value.erase(remove_if(value.begin(), value.end(), isspace), value.end());
                    SetParameter(paramName,value);
                }
                else{
                    error_message = "Parameter name <" + tokens[0] + "> is not registered. Check the input file line: ";
                    READING_ERROR(error_message.c_str(),counter);
                    delete [] buffer;
                    exit(1);
                }
        }
        ++counter;
    }
    if(empty)
    WARN("The input file is empty. Program executed with default parameters.\n");
    delete [] buffer;
    infile.close();
    return reading;
}

bool FileReader::readParticleFile(cl_uint& N,cl_real*& masses,cl_real4*& positions,cl_real4*& velocities, const std::string& filename)
{
    std::ifstream infile (filename.c_str(), std::ios::in);
    const int max_chars_per_line = 512;
    char *buffer(new char[max_chars_per_line]);
    std::vector<std::string> tokens;
    int n_tokens,counter=1;
    cl_uint N_particles;
    bool empty=true;
    std::string error_message("");

    if(!(infile.is_open() && infile.good()))
    {
        std::cerr << std::endl;    
        std::cerr << "ERROR: Failed to open file: " << filename << std::endl;
        std::cerr << std::endl;
        return false;
    }

    infile>>N_particles;
    N=N_particles;
    positions = new cl_real4[N];
    velocities = new cl_real4[N];
    masses = new cl_real[N];
    for(cl_uint i=0;i<N_particles;++i)
    {
      infile.getline(buffer, max_chars_per_line);
      tokens = tokenize(buffer, " \t\r\n");
      n_tokens=tokens.size();
    
      //allow for empty lines
      if (n_tokens == 0) {
                ++counter;
		--i;
                continue;
      } // and allow for comments
      else if (tokens[0][0] == '#') {
                ++counter;
		--i;
                continue;
      }
      else if (n_tokens < 7)
      {
	delete [] positions;
	delete [] velocities;
	std::cerr << std::endl;    
        std::cerr <<RED<< "ERROR: Bad specification of particle at line " << counter <<" of file "<<filename<< std::endl;
        std::cerr <<NONE<< std::endl;
	return false;
      }
      else
      {
	empty=false;
	//mass
	masses[i]=atof(tokens[0].c_str());
	//positions
	positions[i].s[0]=atof(tokens[1].c_str());
	positions[i].s[1]=atof(tokens[2].c_str());
	positions[i].s[2]=atof(tokens[3].c_str());
	positions[i].s[3]=0.0;
	//velocities
	velocities[i].s[0]=atof(tokens[4].c_str());
	velocities[i].s[1]=atof(tokens[5].c_str());
	velocities[i].s[2]=atof(tokens[6].c_str());
	velocities[i].s[3]=0.0;
	++counter;
      }
    }

    delete [] buffer;
    infile.close();
    if(empty)
      WARN("The particle file is empty. No particles allocated.\n");
    return true;

}
